package com.wnxy.sddfp.bank.service.impl;
import java.time.Duration;
import java.time.LocalDateTime;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnxy.sddfp.bank.entity.TradeLog;
import com.wnxy.sddfp.bank.mapper.TradeLogMapper;
import com.wnxy.sddfp.bank.service.ITradeLogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wnxy.sddfp.common.enums.BorrowRecordStatusDTO;
import com.wnxy.sddfp.common.exception.Assert;
import com.wnxy.sddfp.common.exception.BusinessException;
import com.wnxy.sddfp.common.model.dto.bank.BankCardAddDTO;
import com.wnxy.sddfp.common.model.dto.bank.SignatureDTO;
import com.wnxy.sddfp.common.model.dto.bid.BankBidDTO;
import com.wnxy.sddfp.common.model.dto.bid.BidBalanceDTO;
import com.wnxy.sddfp.common.model.vo.bid.LoanVo;
import com.wnxy.sddfp.common.model.vo.trade.BorrowRecordVo;
import com.wnxy.sddfp.common.result.enums.impl.ResultCode;
import com.wnxy.sddfp.common.util.BaseResult;
import com.wnxy.sddfp.common.util.EncryptUtil;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * <p>
 * 托管系统交易记录表 服务实现类
 * </p>
 *
 * @author Jet
 * @since 2023-04-27
 */
@Service
public class TradeLogServiceImpl extends ServiceImpl<TradeLogMapper, TradeLog> implements ITradeLogService {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @SneakyThrows
    @Override
    public SignatureDTO proxyBindCardLog(BankCardAddDTO bankCardAddDTO) {
        //1、保存交易记录
        TradeLog tradeLog = new TradeLog();
        tradeLog.setReqCode(bankCardAddDTO.getReqCode());
        tradeLog.setReqType("USER_BIND_CARD");
        tradeLog.setObjectId(bankCardAddDTO.getUserId());
        tradeLog.setCreateDate(LocalDateTime.now());
        tradeLog.setSynchronizeStatus(0);
        baseMapper.insert(tradeLog);

        //2、返回
        SignatureDTO signatureDTO = new SignatureDTO();
        signatureDTO.setReqCode(bankCardAddDTO.getReqCode());
        signatureDTO.setPlatformNo("SDDFP");
        signatureDTO.setReqData(new ObjectMapper().writeValueAsString(bankCardAddDTO));
        // 访问银行托管系统的地址
        signatureDTO.setBankUrl("http://localhost:5600/bank/user");

        return signatureDTO;
    }

    @Override
    public void modifyStatus(String reqCode) {
        this.update(
                Wrappers.lambdaUpdate(TradeLog.class)
                        .set(TradeLog::getSynchronizeStatus,1)
                        .set(TradeLog::getConfirmDate,LocalDateTime.now())
                        .eq(TradeLog::getReqCode,reqCode)
        );
    }

    @Override
    public void syncBorrowRecord(BorrowRecordVo borrowRecordVo) {
        // fixme 此处的代码可以通过调用this.common(..)进入重构优化
        //1. 保存交易日志
        TradeLog tradeLog = new TradeLog();
        tradeLog.setReqCode(borrowRecordVo.getReqCode());
        tradeLog.setReqType("CREATE");
        tradeLog.setObjectId(borrowRecordVo.getId());
        tradeLog.setCreateDate(LocalDateTime.now());
        tradeLog.setSynchronizeStatus(0);   // 同步成功修改为1

        boolean saveFlag = saveTradeLog(tradeLog);
        if (!saveFlag) {
            return;
        }

        //2. 调用银行托管系统，同步借款数据
        //2.1 对象转换为json格式数据
        String str = JSON.toJSONString(borrowRecordVo);
        //2.2 对数据进行Base64编码
        String encodeStr = EncryptUtil.encodeBase64(str);
        //2.3 对url进行编码
        String reqData = EncryptUtil.encodeURL(encodeStr);
        //2.4 请求地址
        String url = "http://localhost:5600/service";
        //2.5 发送http get请求，远程调用
        this.sendHttpGet("SYNC_BORROW_RECORD",url,reqData,tradeLog);

    }

    @Override
    public BidBalanceDTO getBidBalance(String userCode) {
        String url = "http://localhost:5600/bid/balance/"+userCode;
        BidBalanceDTO bidBalanceDTO = restTemplate.getForObject(url, BidBalanceDTO.class);
        return bidBalanceDTO;
    }

    @Override
    public void addBankBidAgency(BankBidDTO bankBidDTO) {
        this.common(
                bankBidDTO.getReqCode(),
                bankBidDTO.getBizType(),
                bankBidDTO.getId(),
                bankBidDTO,
                "AGENCY_BID"
        );
    }

    /**
     * 访问银行托管系统公用的方法
     * @param reqCode 请求流水号
     * @param reqType   请求类型
     * @param objectId 业务主键id
     * @param reqObj 请求对象，会转换为json后发给银行托管系统
     * @param params 请求参数，银行托管系统处理指定参数名称、值的请求参数
     */
    private void common(String reqCode,String reqType,Long objectId,Object reqObj,String params){
        //1. 保存交易日志
        TradeLog tradeLog = new TradeLog();
        tradeLog.setReqCode(reqCode);
        tradeLog.setReqType(reqType);
        tradeLog.setObjectId(objectId);
        tradeLog.setCreateDate(LocalDateTime.now());
        tradeLog.setSynchronizeStatus(0);   // 同步成功修改为1

        boolean saveFlag = saveTradeLog(tradeLog);
        if (!saveFlag) {
            return;
        }

        //2. 调用银行托管系统，同步借款数据
        //2.1 对象转换为json格式数据
        String str = JSON.toJSONString(reqObj);
        //2.2 对数据进行Base64编码
        String encodeStr = EncryptUtil.encodeBase64(str);
        //2.3 对url进行编码
        String reqData = EncryptUtil.encodeURL(encodeStr);
        //2.4 请求地址
        String url = "http://localhost:5600/service";
        //2.5 发送http get请求，远程调用
        this.sendHttpGet(params,url,reqData,tradeLog);
    }

    private boolean saveTradeLog(TradeLog tradeLog) {
        // 根据请求流水号查询
        String reqCode = tradeLog.getReqCode();
        TradeLog result = queryByReqCode(reqCode);

        // 如果查询结果为空，保存交易日志
        if (result == null) {
            this.save(tradeLog);
            return true;
        }

        // 如果交易记录已经存在，result是不等于null的；所以不执行上面的this.save(tradeLog);
        // 所以，方法参数中tradeLog对象的id是null，导致后面远程调用托管系统后修改交易记录状态失败
        if (tradeLog.getId() == null) {
            tradeLog.setId(result.getId());
        }

        // 业务操作的幂等性保证： 一个业务，不管执行多少次，结果都是一样的。
        // 查询会不会存在幂等性问题？不会。  添加呢？ 会。

        // 如果交易记录已经存在，但是数据还未同步完，此时就存在并发的情况
        if (result.getSynchronizeStatus() == 0) {
            // 利用redis解决重复点击的问题，只需要记录下点击次数即可。
            Long num = redisTemplate.opsForValue().increment(reqCode);
            if (num == 1) {
                redisTemplate.expire(reqCode, Duration.ofSeconds(20));
                return true;
            }
            if (num > 1) {
                // 说明该reqCode已经在redis中存在过了，重复点击
                throw new BusinessException(ResultCode.SYSTEM_INNER_ERROR);
            }
        }

        // 交易日志已经存在，返回false，不会继续后面请求银行托管
        return false;
    }

    private TradeLog queryByReqCode(String reqCode) {
        return this.getOne(
                Wrappers.lambdaQuery(TradeLog.class).eq(TradeLog::getReqCode,reqCode));
    }

    private BaseResult sendHttpGet(
            String serviceName,String url,String reqData,TradeLog tradeLog) {
        // 拼接url地址
        StringBuffer sb = new StringBuffer();
        sb.append(url).append("?serviceName=").append(serviceName)
                .append("&reqData=").append(reqData);

        // 发送rest请求, 返回json字符串
        String responseBody = restTemplate.getForObject(sb.toString(), String.class);
        BaseResult baseResult = JSON.parseObject(responseBody, BaseResult.class);
        // TODO 判断状态码必须是成功的状态码
        if (!"200".equals(baseResult.getRespCode())) {
            throw new RuntimeException("银行同步数据失败！");
        }

        // 修改交易日志中同步状态
        tradeLog.setSynchronizeStatus(1);
        tradeLog.setResponseData(responseBody);
        tradeLog.setConfirmDate(LocalDateTime.now());
        // 执行修改
        updateById(tradeLog);

        return baseResult;
    }

    @Override
    public void confirmLoan(LoanVo loanVo) {
        this.common(
                loanVo.getReqCode(),
                "CONFIRM_LOAD",
                loanVo.getId(),
                loanVo,
                "CONFIRM_LOAN"
        );
    }

    @Override
    public void updateBorrowRecordStatus(BorrowRecordStatusDTO brsDTO) {
        this.common(
                brsDTO.getReqCode(),
                "",
                brsDTO.getBorrowRecordId(),
                brsDTO,
                "UPDATE_BORROW_STATUS"
        );
    }
}
